index.md

Classic FPS Pack

The Classic FPS Pack developed by Ajay Venkat and Thomas Brush, was created as a way to make developing Simple FPS Games easier. Most FPS Assets are bloated and hard to understand, this pack contains scripts that are easy to modify and prefabs that are easy to understand to help you quickly setup a prototype.

It is currently being used in an upcoming indie game Father by Thomas Brush.

Recommended Approach:

  1. Video Documentation for General Learning & Conceptual understanding of pack
  2. Checking out Demos and Prefabs to understand things fit together
  3. Scripting API for understanding scripts & changing functionality

Setup

Before following any documentation ensure that the setup is completed. To setup, use the Classic FPS > Setup editor and follow all the steps to have the necessary layers, tags, physics settings etc.

Ensure you go through one by one and do these, each one is important for the pack to function (except 4):

  1. Setup Tags : Setup all the tags needed for the Pack

  2. Setup Layers : Setup all the layers needed to run the Pack

  3. Setup Physics Collisions : Setup all the layer vs layer collisions (ex. Enemy doesn't collide with Enemy)

  4. Setup Fixed Timestep : Make the game physics run more smooth

  5. Add Scenes : Add all the demo scenes to Build Settings

  6. Setup Active Input Handling : This ensures that both the new and old input system are used so that all systems can be used with this pack. [Restart Required!]

Video Documentation

This pack has been documented in 2 forms, both in video form as well as written form. The video documentation contains information about each of the systems within the pack and will give you a good starting point to use them.

This site will give a more detailed account of every script in the pack and give you the groundwork to extend it.

Link to Playlist: Video Documentation Playlist

Scripting Documentation

This section covers the following:

  1. Every script in the pack & what it does
  2. How to access the script
  3. The public functions/variables in the script
  4. General usage guidelines

Lets Get Started!

Prefabs & Demos

There are demo prefabs and scenes show casing all of the features and scripts talked about above, please check them out and use them as a starting point for creating your own prefabs.

Dialogue.md

Dialogue

The Dialogue is a Scriptable Object that holds all of your Dialogue data. You can create a new Dialogue by right clicking on your Project folder and Create > Classic FPS > Dialogue.

In this Dialogue object you can fill out all the Dialogue you need and serialize it and deserialize it into a text file.

Here is a good tutorial on how to create new Dialogue: Creating New Dialogue

The Dialogue is just a holder of Dialogue Interactions, when you use a Dialogue Scriptable Object, in the Inspector a custom editor will be used to allow you to create/delete/edit the Dialogue Interactions.

The Dialogue object once you have a reference to it can be queried about different information.

Public Variables

  1. interactionName: Name of the interaction between Player and NPC to store as Text File.

  2. PlayerName: The name of the player, default "Player"

  3. NPCName: The name of the NPC, default "NPC"

  4. interactions: A List of DialogueInteraction which make up this Dialogue

Public Functions

  1. ValidIndex (int index): Is there an interaction at this Dialogue Index

  2. CurrentDialogueChild (bool a, int interactionIndex): Get the a or b child of the interactionIndex.

  3. bool CurrentDialogueHasOptions (bool a, bool b, int interactionIndex): Get whether or not a, b, or both exist for interactionIndex index.

  4. List Next (currentIndex): Provide list of Dialogue Interactions given current index.

Functionality

Most of the time you will not need to interface directly with the Dialogue object, there are other classes that will handle this information.

DialogueInteraction.md

Dialogue Interaction

The Dialogue Interaction contains the actual dialogue data such as the text spoken, who is speaking and the index of the dialogue.

Here is a good tutorial on how to create new Dialogue: Creating New Dialogue

Public Variables

  1. isPlayer: Does this dialogue belong to the player?

  2. waitTime: How long to wait before moving to the next dialogue interaction

  3. Line: The dialogue line to show on screen

  4. Commands: List of strings which is the metadata of the current dialogue, this is mostly used for things such as gifting keys during a conversation. Take a look at Key Dialogue.

    When entering this within text file you can do it like this:

    - This is a dialogue line [COMMAND1, COMMAND2]

    Or in the editor you can edit it through comma seperated values.

  5. sfx: The sound effect to play, this can be the voice acted line for this Dialogue. In the Editor you will be able to match the waitTime with the dialogue.

Index Values

The Dialogue object holds a 1 dimensional list of Dialogue Interactions, so to traverse this list with a parent child relationship each object should keep track of itself and it's children interactions.

  1. index: Current index of this interaction

  2. childAIndex: Index of the first child of this interaction

  3. childBIndex: Index of the second child of this interaction

  4. parentIndex: Index of th parent of this interaction

Limitations

Limitations of this system include that each interaction has a maximum of 2 possible pathways, this is not dynamic at the moment. This might take a bit of work to extend; however the way that the serialization & deserialization works this shouldn't be too difficult.

DialogueProcessor.md

Dialogue Processor

The Dialogue Processor is found on the Canvas and controls all the UI elements of the Dialogue and also handles moving through a Dialogue and sending back callbacks.

This is directly executed by the DialogueRunner and most of the time you will not need to work with this script. However you can and should change the UI that is linked to this processor to change the visuals.

Public Variables

These options are for the input & SFX of selection.

  1. OptionSelected: The Sound Effect to play when an option is selected.

  2. OptionAInput: The Input Action (key) to press to select the first option.

  3. OptionBInput: The Input Action (key) to press to select the second option.

Public Static Functions

These functions can be used to directly interfere with the UI of the Dialogue:

  1. Disable(): Disables the current dialogue by force.

  2. BeginDialogue(Dialogue dialogue, string NPCName, string PlayerName, float waitTime, bool playPlayerReplies, System.Action OnDialogueStarted, System.Action OnInteractionCompleted):

    You can begin the dialogue manually by passing in all of these variables that are usually found on the DialogueRunner, please look at DialogueRunner to understand how to call this function.

DialogueRunner.md

Dialogue Runner

The Dialogue Runner is a MonoBehaviour that can be attached to Game Objects and can execute Dialogue; it is the interface between the Player Input and the running of dialogue on UI.

It also has a list of public functions that you can use to control how Dialogue is executed and actions to run during and after the execution of Dialogue.

In order for this to work you will need any sort of Trigger attached to the same object.

Here is a good tutorial on how to use the Dialogue System: Dialogue System

The References to the UI itself is now held in the UIManager script, which should also be on the Canvas.

Public Variables

  1. triggerEnabled: Is the trigger for this object enabled

  2. source: AudioSource from which to play the SFX on the Dialogue

  3. autoRunWhenPlayerEnters: Auto-run the Dialogue when the Player enters the trigger

  4. diableAfterLeavingTrigger: Auto-disable the Dialogue when the Player leaves the trigger

  5. runOnce: Ensure that the Dialogue only runs once through

  6. playPlayerReplies: Should you play the Player's options once they have selected it or just move to the next NPC dialogue

  7. DialogueCompletedEvent: This is an UnityEvent meaning you can attach any function on other scripts to this callback, that function will be called when the Dialogue is completed.

  8. DialogueRunEvent: This is also an UnityEvent meaning you can attach any function on other scripts to this callback, that function will be called everytime a new DialogueInteraction is shown on screen and it will also pass the DialogueInteraction to the parameters of that function.

    This can be used to check the commands at every interaction to execute some functionality.

  9. runnableDialogues: The list of Dialogues that can be executed, you can attach the scriptable objects you created in the project to this list.

  10. defaultRunDialogue: This is the index of the dialogue in runnableDialogues to execute by default when the player enters the trigger or uses the input.

  11. dialogueRunInput: The input to use to execute this Dialogue, this is another way to begin the Dialogue; the player still has to be within the trigger.

  12. overrideDialogueInputExecution: Prevents input from working so that another script can take over the execution of the Dialogue Runner.

Public Functions

  1. ExecuteDialogue (int index, bool requirePlayerToBeInTrigger): From another script you can execute a Dialogue at a certain runnableDialogues index, you can also ensure the player is in the trigger when this is executed.

  2. ExecuteDialogue (Dialogue interaction, bool requirePlayerToBeInTrigger): Here you can pass a Dialogue directly into the runner and ensure the player is within the trigger.

  3. StopCurrentDialogue(): This will stop the currently executing dialogue.

DialogueUtils.md

DialogueUtils

The Dialogue Utils are in charge of serializing and deserializing the Dialogue. At the moment Dialogue is serialized into a text file which usually looks something like this:

- Hello! Are you looking for an orange key?
    - No...
        - Okay, Bye!
    - Yes!
        - Okay, take this ORANGE key!! [KEY]
            - Thanks random NPC!

You can use the public static functions of the Dialogue Utils if you want to serialize and deserailize yourself.

Static Functions

You can access these functions like so:

DialogueUtils.StaticFunction(parameter);
  1. string Serialize (List interactions): Returns a string which is the serialized text given a Dialogue Interaction list.

  2. List Deserialize(string s): Given a string it will return a list of Dialogue Interactions that you can attach to a Dialogue object.

Door.md

Door

The Door script is attached to any object that can be triggered with a Key object, you can even use it for treasure chests and padlocks.

The Door is derived from State so its tracked by the SaveManager.

Public Variables

  1. saveDoorState: Whether or not to save the state of the door.

  2. keyReference: A drop down where you can select or type the ID of the key you want to use to unlock this door. You can select anything from the KeyManager.

  3. requiresKey: Do you need a key to open this door?

  4. doorOpen: SFX to play On Door Open.

  5. dialogueRunner: A reference to the DialogueRunner component on the GameObject that will play dialogue.

  6. executeDialogue: Reference to the actual Dialogue object to run when the door doesn't open.

  7. openDoor: The Input Action to use when opening the door (you can change this in inspector).

Functionality

The Door will contain an animator with the boolean parameter opened; when this is set to true the Door's open animation will play.

When the player enters the trigger and then presses the openDoor input action, the door will check if the Player has the key needed and open the door.

If not then it will play the 'no key' dialogue. You can extend this to play dialogue when the door opens.

Tracked State

The only tracked state is opened, so if the Player opens the door they will not have to keep reopening it after they die; it can remain open.

KeyDialogue.md

Key Dialogue

The Key Dialogue is a script that be attached to an NPC alongside a DialogueRunner to allow an NPC to gift the player a key depending on the choices they make.

Please watch this video understand how to attach actions to a dialogue : Attaching Dialogue Interactions

The Key Dialogue also derives from state so it is tracked by the SaveManager.

Public Variables

  1. keyReference: The Key ID that should be given to the player when the dialogue interaction reaches the key gifting.

  2. dialogue: Dialogue Runner reference.

  3. onlyGiftOnce: Prevent the NPC from gifting the key more than once.

Functionality

When the player runs the dialogue, at every step of the dialogue the GiftKey (DialogueInteraction interaction) function in this script is called. This is done by attaching it as a callback on the DialogueRunner.

When the function is called it also returns the current interaction the dialogue is at, the interaction contains all the information about that dialogue point including all the commands attached to it.

The GiftKey function checks the following:

if interaction.Commands.Contains("KEY"), if this is true then it will gift the player the key. This is a command and they can be attached to any section of the dialogue in the following way.

- Hello have this key [KEY]

If the player has never recieved a key before the Key Dialogue will play the dialogue at the 1 index of the dialogues on the Dialogue Processor. Otherwise it will play the 2 index dialogue.

Please take a look at the prefabs in the Dialogue folder to understand how to set up this system fully.

Tracked State

The tracked state ensures that the NPC only provides the player with the key once, so they can't restart the game and ask for another key.

Sample Dialogue

This is a sample dialogue tree structure that can gift the player a key:

  • Hello! Are you looking for an orange key?
    • No...
      • Okay, Bye!
    • Yes!
      • Okay, take this ORANGE key!! [KEY]
        • Thanks random NPC!

KeyPickup.md

Key Pickup

This is a class derived from the Pickup class so it tracks the state of whether or not this item has been picked up.

Note: This item needs a trigger.

This will pickup a new key and add it to the collected keys, then these keys can be used to open doors.

The keys are appended through the PlayerStatistics script using the CollectKey(string keyID) function.

Public Variables

  1. keyReference: ID of the Key that this pickup should give to the player, the ID has to be something in the Key Manager; there should be a drop down to select.

KeyReference.md

Key Reference

The Key Reference is just a class with a string inside it, it is serializable and the only purpose is to allow a custom editor to be generated.

The Custom Editor will check if there is a Key Manager in the project and develop a drop down that you can select the key from, this makes adding Key Selection variables easier in the code.

However, if you have multiple Key Managers it will revert to a text field.

ClassicEnemy.md

Classic Enemy

The Classic Enemy is used on two enemy types:

  • The Shooter Enemy: Chases the Player and shoots projectiles at them, when it gets closes enough performs attacks.

  • The Walker Enemy: Simply chases the Player until close enough then attacks.

This class derives from the Enemy script so the properties and methods from that script can be used here.

Public Variables

  1. injuryRadius: The radius at which the Enemy can attack the player at a close distance, this is an attack form such a punching.

  2. injuryDelay: The delay between each attack the Enemy makes.

  3. damageByProximity: The amount of damage the Player will take when the Enemy is attacking at close range.

  4. aimSpeed: The speed at which the Enemy can aim their weapon.

  5. shootProjectiles: Does this Enemy shoot any projectiles.

  6. projectilePrefab: The projectile prefab to shoot.

  7. gunModel: This gun model reference will point towards the player with the speed of aimSpeed.

  8. projectileSpawnPoint: Spawn point of the projectile which is a child of the gunModel so its facing in the right direction.

  9. projectileSpeed: Initial speed of the projectile.

Functionality

The Follow() method is called when the Player is in the trigger on Update and the agent's location is set to the Player; all animations are triggered and shooting is triggered from this method as well.

The Update loop is in the Enemy script from which this script is inherited from.

DamageableEntity.md

Damageable Entity

The Damageable Entity is a base class that is extended to other objects in this pack such as the Breakable Object and Enemy.

It is based on the State class so it will be tracked by the SaveManager.

The Damageable Entity contains a few functions that can be overriden by children classes that mainly cover taking damage and dying.

Public Variables

  1. health: Initial health of the entity.

  2. respawnOnLoad: Ensure that the death of this entity is not permenant.

  3. hitParticles: Emit hit particles when the entity takes damage.

  4. droppablePrefabs: List of prefabs that can be dropped when the entity dies.

  5. chanceOfDrop: The chance that this entity drops something when it dies.

  6. alwaysDrop: Always drop some prefabs on the death of the entity.

  7. dropAllItems: Drop all items in droppablePrefabs on death.

  8. spawnOffset: Offset of the droppable prefab spawn on death.

  9. onTakeDamage: Sound effect to play on take damage.

  10. onDeath: Sound effect to play when the entity dies.

Override Functions

The override functions are used by any scripts that inherit this script.

  1. TakeDamage (float damage, float delay): What functionality to execute when the entity takes damage, the default behaviour is to emit hit particles, take away the health, check for death and play the sound effects.

  2. Die (bool spawnDrops): What to execute when the entity dies, the default behaviour is to spawn the drops and then set the GameObject to disabled.

Tracked State

The only property tracked in this state is the health of the entity, if the health is <= 0 after it is loaded in then it will be killed automatically; however no drops will spawn.

Basically, the system will ensure the entity remains dead or destroyed until saves are cleared.

DroneEnemy.md

Drone Enemy

The Drone Enemy script is only used on one demo enemy, the Drone Enemy; the Drone Enemy is able to fly above in the air and shoot homing missiles at the Player.

This class derives from the Enemy script so the properties and methods from that script can be used here.

Public Variables

  1. aimSpeed: Speed at which drone can lock onto the Player.

  2. followSpeed: Speed at which the drone can follow the Player.

  3. stoppingRadius: Radius at which the drone stops from the Player.

  4. injuryDelay: Delay of each projectile shot.

  5. hitCollider: This is the collider of the drone, the center of the collider needs to be relaigned with the geometry each frame to ensure the collisions remain working.

  6. geometry: The visual geometry of the Drone.

  7. projectilePrefab: Prefab that will be spawned as the Projectile.

  8. projectileSpawnPoint: The spawn point of the Projectile.

Functionality

The Follow() method is called when the Player is in the trigger on Update and the agent's location is set to the Player; all animations are triggered and shooting is triggered from this method as well.

The Update loop is in the Enemy script from which this script is inherited from.

Enemy.md

Enemy

The Enemy is a base class that is used for the two types of default enemies in this pack; the script itself is derived from DamageableEntity meaning it can take damage and die.

Public Variables

  1. trigger: The trigger that the player enters in order to grasp the attention of the Enemy.

  2. damageByThroughObjectsMultiplier: The damage provided to the Enemy is based on the velocity of an object thrown at it; this value multiplies that by a certain amount.

  3. agent: The NavMesh agent that the is going to traverse over.

  4. animator: The Enemy animator that will play its Walking & Attacking animations.

  5. graphics: The Graphics object on the Enemy (ex. Capsule).

Override Methods

These are the methods that can be used by classes that inherit from Enemy:

  1. Follow (): The behaviour when the Enemy is in the follow state; when the Player has entered the trigger.

  2. Attack (bool longRange): The behaviour of the attack when the player is close to the Enemy and far away.

Functionality

It is important that the Enemy is standing on a NavMesh and the NavMesh is baked.

BreakableObject.md

Breakable Object

A Breakable Object is derived from DamageableEntity meaning its health is tracked and it is able to take damage and eventually die.

Breakable Objects are items such as Crates that can be attacked by weapons or be destroyed by throwing them.

Damageable Entities including this one are able to drop objects on their death, and play death and damage SFX.

Public Variables

Most of the public variables are drawn from the DamageableEntity script; however there are a few variables:

  1. damageVelocityMultiplier: The amount to multiply the velocity of the object on impact to take damage with.

  2. minimumVelocityForImpact: Minimum amount of velocity needed to take damage.

PushableObject.md

Pushable Object

By default the Player can move Rigidbodies; however sometimes this can lead to unstable or unwanted behaviour. The Pushable Object script allows you to control some of those parameters.

This script is also responsible for allowing an Object in the scene to be picked up and thrown either through a Gravity Gun or by the default player pickup.

It also handles things such as playing an impact sound effect.

The PlayerPhysics script interacts with the Pushable Objects the most; especially in with the pushPower and pushableObjectVelocityMax variables; please read PlayerPhysics first to understand the effect on Pushable Objects.

Functionality

A few things affect the movement of the Pushable objects:

  1. pushPower of PlayerPhysics: How much force to apply to the Object that you are pushing up against.

  2. pushableObjectVelocityMax of PlayerPhysics: If this is not limited then the Player can add infinite force to an Object.

  3. speed of PlayerController: How much speed the player has when pushing against this object.

  4. The properties of the Rigidbody attached to the Pushable object

    • The Mass of the Rigidbody that the Player is trying to push
    • The Angular Drag of the Rigidbody that the Player is trying to push
    • The Drag of the Rigidbody that the Player is trying to push

Public Variables

The public variables mostly affect the behaviour of the object:

  1. canBePickedUp: This determines whether or not the player can pickup the object either by default or through a Gravity Gun.

  2. maximumVelocity: Safety option to ensure object never goes above a certain velocity (angular velocity is also impacted here).

  3. minimumVelocityBeforeAudio: The minimum velocity on impact to create an impact sound.

  4. objectHoldingOffset: This is a manual value that determines how far away to hold the object when it is picked up.

  5. precalculateBounds: If this is true then the previous parameter is rendered useless; the precalculate bounds attempts to estimate where to place the object depending on its size.

  6. impactSound: Sound when the object collides with something.

  7. onShootSound: Sound to play when the object is shot.

  8. waitBeforePlayingSFXAgain: Minimum delay between two SFX.

Drag Forces

In the Rigidbody component you will only have access to one drag parameter; so if you want rigid movement on X/Z axis without affecting how the object falls; this is not possible.

This is why further drag options are provided here to affect individual axis:

  1. xAxisDrag, yAxisDrag, zAxisDrag: The drag on each of the axis.

Creating a Pushable Object

For example, you want a simple cube that you can push on the ground. Simply create a Cube GameObject, then follow these steps:

  1. Assign the PushableObject Component to the object

    • You can modify the Velocity Drag to individually affect the different axis' Drag after the Player loses contact with the object (can prevent sliding effect).
  2. Ensure there is a Collider on the object

    • A primitive Collider is recommended here (Cube, Sphere, Capsule) as the forces can behave unrealistically on more complex geometry.
  3. Ensure there is a Rigidbody on the object

    • If you want the Cube to just move along the surface of the ground then you can Freeze Rotation on the _X_ and _Z_ axis.

    • If you don't want the Cube to rotate too much then you can bump up the Angular Drag.

The GameObject would behave something like this:

Lets bump up the X & Z drag on the Pushable Object script, this will lead to some nicer behaviour:

Optimisations

The Pushable Object script is heavily optimised, it does not have an Update() function, it only runs when the Player is pushing the object, so a scene can contain many Pushable objects.

EndUI.md

End UI

The End UI has been used in the finish screen of the sample game, the only thing it does is; on Awake it calls SaveManager.ClearAllSaves() to clear the saves.

KeyUI.md

Key UI

The Key UI is responsible for displaying all the keys you have picked up, this is is on the Canvas.

Public Variables

  1. keyPanel: This is the panel which represents the starting point of the keys on the Canvas.

  2. keyElementPrefab: Prefab of the key element, simply an Image UI (the image is filled out by the KeyManager).

  3. horizontalPadding: Padding of new keys that are added.

  4. horizontalLength: Length of the UI image (ex. default 40px).

Public Functions

  1. UpdateUI (List keys): Update UI given a list of keys, the strings are the IDs of the keys which allow the UI to pull images from the Key Manager.

SaveUI.md

Save UI

The Save UI is used mainly on the Menu to coordinate the behaviour of the Menu.

Public Variables

  1. startGameButton: The button to start the game

  2. clearProgressButton" Button to clear the progress of the game

Public Functions

These public functions are usually hooked up to the buttons in the screen to execute different behaviour.

  1. DeleteSavedProgress(): Clears the saves in the game and then refreshes the UI.

  2. LoadPlayerCurrentLEvel(): Finds and loads the last level the player saved on.

  3. RefreshUI: Refreshes the text on the buttons that are hooked up.

GroundSound.md

Ground Sound

A Ground Sound is used in the SFXManager to represent the different ground types the player can walk on and the footsteps to play on that surface.

Public Variables

  1. TerrainLayerNameOrTag: The name of the terrain layer or the tag that you have attached to the surface.

  2. isTerrainLayer: Here you tell the system whether the name you provided above was a terrain layer or not.

  3. footsteps: A list of AudioClips to play at random when walking on this surface.

The logic to play these sounds are found in the PlayerSFX.

PlayerSFX.md

Player SFX

The Player SFX stores all the Player's main sounds and also the audio source of the player. It also houses the logic for playing the footsteps depending on the surface type.

Public Variables

  1. playerAudioSource: Reference to the audio source of the player.

  2. controller: Reference to the player controller, this will be imported automatically in future.

Sounds

  1. onSpawnSound: Sound to play when the player spawns.

  2. jumpSound: Sound to play when the player jumps.

  3. landSound: Sound to play when the player falls off a ledge/jumps and lands.

  4. onTakeDamage: Sound to play when the player takes damage.

  5. onDeath: Sound to play when the player dies.

All these are called from various different scripts, mainly the following:

  1. PlayerStatistics.cs: On Take Damage, On Death, On Spawn Sound
  2. PlayerController.cs: Jump Sound, Land Sound

Public Functions

  1. PlayFootstepSound: You can call this by finding the PlayerSFX, through GameManager.PlayerSFX.instance.PlayFoostepSound() or by using a PlayerSFXReference.

    This first identifies if you are located on a terrain and the ground the player is standing on a terrain; then it checks the terrain layer you are currently on to figure out what sounds to play.

    Then it loops the sounds, randomly selecting from the list.

PlayerSFXReference.md

Player SFX Reference

This is only used when attached to the weapon's animators. It exposes a public function to call the footsteps of the player through the animation.

It's a very simple script that is used just to expose 1 function.

  1. PlayGroundSFX(): Finds PlayerSFX then calls PlayFootstepSound().

    You can call this function through an Animation Event in an animation.

Sound.md

Sound

This class is a simple holder for Audio, it is used throughout the pack to make it easier to play Audio.

A sound is usually included as a public variable in a script for the user to access and change:

public Sound hitSFX;

Advantages

This gives you an easy way to attach sounds and play them with a volume that you define, also you can always play the sound without worrying that it might be null as that is checked within the functions.

Public Variables

A Sound contains the following variables:

  1. sound: The AudioClip to play
  2. defaultVolume: The volume at which to play this audio (Slider) This can be overriden when the sound is played.

Public Functions

A sounds public functions can be used to easily play it in different ways.

You can access a public function like the following:

hitSFX.PublicFunction (vars);
  1. PlayAt (Vector3 position, float volume = -1f): This plays this SFX at a certain position with a given volume, if you don't enter a volume it will use the defaultVolume provided.

  2. PlayFromSource (AudioSource source, float delay = 0f, float volume = -1f ): This plays the SFX from a certain AudioSource with a delay and given volume; if no delay is inputted it will play instantly; if no volume is given then it will use defaultVolume.

It is encouraged to extend the functionality of this class found in AudioClasses.cs to incorporate more logic.

TerrainSurface.md

Terrain Surface

The Terrain Surface is a helper class with some static functions to help identify the texture the player is standing on.

Public Static Functions

You can access a public static function like so:

string surfaceName = TerrainSurface.GetMainTexture(new Vector3(0,0,0));
  1. GetMainTexture (Vector3 pos): Given world position it will return the name of the texture you are currently standing on; you can change behaviour based on this.

DefaultPlayerWalkAnimator.md

Default Player Walk Animator

Usually the walk animation & walk SFX are played through the actual Weapon's animator. However, there are cases where the Player does not have a weapon equipped; in this scenario the SFX still needs to continue playing.

On the Update() method of this script, if there is no active weapon then the Animator on the Gun Mount GameObject is played.

It's a default animator that plays a default walk animation; the footsteps are determined based on the Ground Type.

PlayerCameraController.md

Player Camera Controller

The Camera is primarily controlled in the Player Camera Controller Component on the Player, it handles the following logic:

  • Capturing Mouse events through the new Input System
  • Orienting the Pivot GameObject based on the movement of the Mouse
  • Locking and Toggling Visibility of the Cursor
  • Tilting the Player based on Horizontal Movement
  • Increasing Field of View on Sprinting

Components

There are two main components that control the Camera, the Cinemachine Controller which has properties that modify the initial camera & follow behaviour; and the Player Camera Controller which handles logic.

Cinemachine Controller

Learn more about Cinemachine Controller

Camera Controller

It's also important to know how the Camera is being modified in the Player Camera Controller.

Let's run through the different properties:

  1. Pivot Transform : A reference to an empty GameObject in the center of the Player.

  2. Virtual Camera : A reference to the Cinemachine Virtual Camera we covered in the last section.

  3. Lock Rotation : If this is enabled it will just prevent the rotation from changing, this can be useful if we need to override the rotation of the Camera.

  4. Locked : This is referring to the Cursor Lock State, there are 3 modes that can be selected. Locked is preferred so that the Cursor does not get in the way of testing the game.

    1. Locked : The Cursor cannot move when in the Game Window
    2. Confined : The Cursor cannot leave the Game Window
    3. None : The Cursor is left without change
  5. Visible : Whether or not the Cursor is visible on the screen.

  6. Pitch Min Max : The minimum and maximum rotation on the up and down axis for looking.

  7. Mouse Sensitivity : How much a movement in the mouse affects the rotation of the Camera.

  8. Rotation Smooth Time : How quickly the rotation of the Camera reaches its desired value.

  9. Camera Influenced By Player Input : Whether or not the Player Camera Controller should listen to inputs from the controller to change its behaviour. Also this can be set to false if this Player is to be repurposed for a Third Person Controller.

  10. FOV Zoom Amount : How much to increase the FOV when the Player starts sprinting.

  11. Tilt Amount : How much to tilt the character when a horizontal movement is detected (inspired by Dusk).

  12. Tilt Speed and FOV Zoom Speed : How quickly the two respective parameters above are influenced.

Weapon Camera

In FPS Games there is a common problem whereby the Weapon will clip into the walls when the Player walks too close to walls. To combat this issue there are two Cameras rendering in the Player Controller.

The Camera which is controlled by the Cinemachine component and the Weapon Camera. The Weapon Camera is a child of the main Player Camera so it follows the same rotation/position.

The Weapon Camera is a property of the PlayerCameraController.

The Weapon Camera renders only items with the Layer: 'Weapon'. While the main Camera renders items without the Layer 'Weapon'.

With this combination the Weapon Camera will be rendererd ontop hence ensuring that the Weapons will always be ontop.


What exactly does the Tilt and FOV Increase do?

By increasing the FOV when sprinting it gives a feel of speed increasing by slightly increasing the amount that you can see in the corners. By tilting the Camera while moving horizontally it provides a natural feel to the Camera and a sense of motion.

PlayerController.md

Player Controller

The PlayerController Component is the 'brain' of the whole player, it joins together the input, physics, logic all into one script. It's very easy to setup and configure.

Public Variables

  • Forward Direction: Just a reference to the ForwardDirection GameObject in the Hierarchy.

  • Camera: Just a reference to the Camera GameObject in the Hierarchy.

  • Walk Speed: The default speed for the Player.

  • Sprint Speed: When the key to enable sprinting is pressed, this will become the new speed.

  • Jump Speed: The initial speed of the Jump that will be sent to the Player when the key binded to the Jump is pressed.

  • Slope Slip Speed: The speed of the Player when it is standing on a slope that exceeds the slopeLimit defined in the CharacterController. The speed controls how fast the Player slips down a slope.

Public Methods

What if you want to modify the functionality of the Player Controller yourself, or if you want to use some of the information captured by the controller?

First get a reference to the PlayerController, then you can access the following functions and variables using a format like this:

GameManager.PlayerController.publicFunction(arguments);

Here are the most important ones:

  1. isStrictlyGrounded() : This tells you precisely if the Player is grounded, it is extremely accurate and hence will be false even on the slightest detachment from the ground.

  2. isApproximatelyGrounded() : This tells you if the Player is standing on some type of stable grounding, this is the function that is used for the Jump functionality. This is done because we need the player to be grounded but if we wait for extreme accuracy then the Player may not be able to jump on rough surfaces.

  3. ApplyVerticalForce (float forceAmount) : This simply applies an immediate vertical force which will eventually be brought down by gravity.

  4. ApplyForce (Vector3 force) : If you have some object that needs to exert some force on the player then you can use this, the input of the Player will still be active so they can fight the force.

  5. ApplyImpulseForce (Vector3 force) : Should be used if you need to exert an immediate and impulsive force that overrides the input.

  6. EnablePlayer() and DisablePlayer() : If for any reason you need to disable/enable the Player then you can use this, no inputs, movement etc. will function anymore. This is useful in situations such as climbing up a vertical wall for a wall climbing system, as the Player Controller will actively prevent you from doing that.

  7. isStandingOnSlope() : Returns whether or not the Player is currently standing on a slope that exceeds the slopeLimit assigned in the CharacterController.

  8. bottomOfPlayerController() : Returns a Vector3 telling you where the exact bottom of the Player is, this can be useful if you want to make your own ground checks.

  9. TeleportPlayer(Vector3 newPosition): Moves the Player to a new position immediately.

Public State Variables

These variables are public however they are intended to be used to understand the state of the Player Controller.

  1. groundHit : This is a RaycastHit variable that returns information about the current ground of the Player.

  2. characterSpeed : The current speed of the character, it fluctuates between the walkSpeed and sprintSpeed.

  3. hasJumped : Whether or not the Player is performing a Jump.

  4. lastVelocity: Last known velocity of the Player (last frame).

  5. airTime: How long the Player has been in the air, it's used to determine when to play the landing sound.

There are a lot more exposed variables that you can use to determine the state of the Player Controller, take a look through the code.

Saving & Loading

After a state has been loaded on a Player Controller it calls the following function:

if (GetComponent<PlayerController>()) GetComponent<PlayerController>().AccumulateSaveState();

The AccumulateSaveState() on the PlayerController will tell the Player Controller that the loading process has been completed; when both the Transform State and Player Statistics state complete loading the Player Controller will fade out the loading screen.

This will ensure there is no jittering when the Player Controller is teleporting when loading in.

The two states that are on the Player Controller are:

  1. Transform State: Tracks player position & camera rotation
  2. Player Statistics: Keys, Health, Weapons etc.

Both of these have to complete loading before the loading screen is released (or no saves to load).

Take a look at the UIManager to change loading screen behaviour.


The script within the Player Controller is very complex and it split into the different logical components, if you need any advice on how to modify the script please contact me:

ajays.workemail@gmail.com

PlayerInputManager.md

Player Input

Configuration

Knowing how to configure the PlayerInput is very important, the PlayerInputManager simply listens to any events triggered by the PlayerInput (different to PlayerInputManager) Component. However, the keys that trigger these events can be defined by you.

Currently these are the controls:

  1. WASD : Movement along two axis (forward/backward and left/right)
  2. Shift : Increases the Player speed to sprinting
  3. Space : Performs the Jump movement
  4. E: Pickup item that is on crosshair
  5. Left Mouse Button: Throw item that is on crosshair (also attack for weapon but that is handled on the Weapon component)
  6. Right Mouse Button: Drop currently picked up item; down

However, what if you need to support this Player on a different system?

To solve this problem the Player utilises the new Unity Input System, you can learn in a lot more detail about it here.

Place your attention on the Inspector window and find the PlayerInput Component, look at the Actions property. The asset found in that field determines what Inputs trigger what functions. You can find this asset in Samples\Sample Presets\Prefabs\Player\Input. You can double click on the asset to open up the Input System Editor.

This is where you will modify Input bindings.

Notice how I have expanded Movement then selected Up. In the right most panel is where I can change the binding for the action, the PlayerInputManager will handle getting information from that new key/joystick/mouse source.

Each of these inputs are mapped to a method in the Player Input Manager, if you look at the Player Input component.

Public Access

  1. GenerateMovementVector(): This takes the current input and gives out the current movement vector of the Player, it also does some extra calculations to the the current ground into account to develop a more smooth movement vector.

You should not call this function in general however as this is called by the PlayerController.

To get the last generated movement vector use the public processedInputs variable. To get the raw input data use the inputData variable.

These can be used to determine for example when to play a walking sound.

PlayerObjectInteractionHandler.md

Player Object Interaction Handler

The Player Object Interaction Handler script is used mainly to handle the pickup/throwing of objects. This is a way to pickup items without the Gravity Gun and through the player's 'hands'.

The Player can pickup items that have the PushableObject script with the canPickup variable set to true.

The Script ensures the following:

  1. When an item is picked up the weapon that the player is holding is unequipped
  2. The object that is picked up is centered and correctly positioned
  3. Multiple objects are not picked up
  4. Handles removing the collider/rigidbody on the item picked up

Input Events

There are multiple events inside the script that are triggered through inputs:

  1. GetPickupRequest(): Called when the user tries to pickup an item with the crosshair pointed over it.

    Default E

  2. GetDropRequest(): Called when the user tries to drop the currently picked up weapon (simply drop it without throwing it)

    Default Right Mouse Button

  3. GetThrowRequest(): Called when the user tries to throw away the object.

    Default Left Mouse Button

These are all triggered through the Player Input component on the Player Controller, they can be modified by changing the Player Input's bindings. This is explained more in the Player Input Manager section.

Public Variables

  1. pickup: This is variable is based of a class called PickupUtils, this script manages all the pickup behaviour; and the variables within this class modify the pickup behaviour.

PickupUtils exists because the Gravity Gun exhibits the same behaviour as the Player Object Pickup, therefore it has been reused.

Go to PickupUtils Scripting Reference to learn how to change all the pickup properties such as pickup distance, crosshair color etc.

PlayerPhysics.md

Player Physics

The PlayerPhysics Component provides some helper functions to the PlayerController that allow it to perform some of the following tasks:

  1. Raycast from the Player
  2. Spherecast from the Player
  3. Check whether it is grounded
  4. Check if the Player is in the wall, if so push it out

Variables

  • Capsule Collider: The collider you want to use to detect wall sticking, it should extend a little bit outside the bounds of the Character Controller's capsule (this is pre-configured).

  • Gravity Scale : How quickly the Player is brought down to the ground, where the scaling is relative to the default Unity gravity (-9.81).

  • Disclude Player : This is the LayerMask ensuring that any Raycasts/Spherecasts from the Player do not intersect with the Player itself.

Note how the Player layer is disabled while everything else is enabled, this is why we set the FPSController and all it's children layers to Player.

  • Reference Base Point : Where the 'bottom-half' of the Player lies relative to (0,0,0) being the direct center of the Player.

  • Base Radius : The radius of the 'bottom-half' of the Player, this is the sphere that will be used to check against the ground for the groundind status.

Collision & Interaction

Collision is handled one of two ways in this pack, either through the Capsule Collider on the Character Controller component or through some amount of sphere colliders you have determined.

The collisions defined here are how it affects the other objects in the scene such as pushable objects, rigidbodies etc.

Setting up these colliders is explained more in this video : Setting up Interactive Objects

The variable in charge of this is:

  • Collision Colliders: Here you can drag in any amount of sphere colliders to detect collisions with.

  • Use Fixed Update: Set this to true if you can afford a very low fixed timestep, otherwise set this to false and the collisions will be claculated on Update.

  • Use Custom Spheres: Set this to true when you are using custom sphere collision, however if you set this to false; then you must set useFixedUpdate to true because the default collision only works on fixed update.

  • Push Power: How much force to exert onto the Rigidbodies the player comes into cntact with.

  • Push Object Velocity Max: The max force that can be exerted on an object to ensure the objects don't fly off really fast.

PlayerStatistics.md

Player Statistics

Player Statistics is based off the State class, this means it is tracked by the Save Manager and hence saved/loaded when a scene is loaded.

More information about how saving/loading works can be found in the Save Manager reference.

The UID of the PlayerStatistics class is simply "PlayerStats", since it does not have the scene name in the UID it is not dependent on the current scene. This means any information tracked in this state will remain throughout different scenes.

Public Variables

These variables can be changed to modify the initial state of the Player:

  1. maxHealth: The maximum health the Player can have, the Player will start with this amount of health & health pickups won't increase health past this amount.

  2. playerOptions: This is the default state of the Player, you can change these variables however if there is a save file then this will be changed to those values.

Health Resetting

The Health is reset back to the Max Health in the following scenarios:

  1. Player Dies & Respawns (Handled in PlayerStatistics Death function) - all information goes back to last save.

  2. The Player switches Level through the LevelSwitcher component (there is an option to disable resetting health on moving to another level)

Public Functions

These functions can be used to find out information & change the state of the player. They can be accessed through the following:

GameManager.PlayerStatistics.PublicFunction();
  1. CollectKey(string keyID): Allows Player to pickup a key.

  2. HasKey(string keyID): Checks if Player has a given key.

  3. UpdateUI(): Updates the UI relating to the health & coin variables stored in the player.

  4. TakeDamage(float damage): Allows player to take a certain amount of damage and handles the death condition & SFX as well.

  5. Death(): Kills the player. If there is an existing save it goes back to that save as 'respawn' behaviour; if there isn't nothing happens; you will have to implement this logic.

    It is highly recommended that the death function is changed to suit the needs of your game.

Tracked Information

The Player Statistics script tracks the following information:

  • The health of the Player
  • The number of coins the Player has
  • The guns the Player has picked up
  • The keys the Player has collected

These are stored in the Stats struct.

On Save

When the state is saved the following is executed:

  • Save all the collected weapons
  • Save all the information such as health, coins & keys

On Load

When the state is loaded the following is executed:

  1. Find all saved weapons and equip them onto the Player
  2. Find all saved keys and collect them
  3. Update the UI
  4. Play spawn noise
  5. Tell the Player Controller that the Player Statistics is loaded

PlayerWeaponController.md

Player Weapon Controller

The Player Weapon Controller handles the interaction between the input of the player and the weapons system, it also handles any requests & queries related to Weapons.

The Player Weapon Controller is found on the Player Controller object alongside the Player Controller.

Public Variables

These variables will control the behaviour of the Weapons system:

References

  1. weaponSoundSource: The AudioSource of the Weapons, this is held on the Gun Mount GameObject on the Player Controller.

  2. defaultWeapons: List of Default Weapons that should be added to the Player, these are UID's of the Weapons on the WeaponManager.

  3. weaponMount: The GameObject on which the Weapon's will be spawned.

  4. defaultCrosshair: The default crosshair icon that will be used when a weapon is not equipped.

Input

These have been pre-created to work with the number mappings & scroll values; however you can modify these:

  1. numberKeys: The input maps for all the number keys (premade).

  2. scrollTolerance: The 'amount' of scroll required to switch to the next weapon.

  3. scrollWheel: The scroll wheel input.

Public Functions

Public functions can be accessed by :

GameManager.PlayerWeaponController.PublicFunction();

Here are some of the core public functions:

  1. ChangeWeapon (int selected): Changes the currently holding weapon to the weapon at the 'selected' index on the collectedWeapons list.

  2. UpdateUI(): Updates the Weapon related UI (Weapon Image, Ammo Count).

  3. CollectWeapon (string ID): Collect a weapon given an ID; this method ensures the weapon has not already been collected and does other cleanup logic.

  4. CurrentHoldingID(): Get the ID of the gun that the Player is currently holding.

  5. GetCurrentWeapon(): This gets the WeaponReference of the currently spawned weapon.

  6. HasActiveWeapon(): Returns true/false depending on whether the Player has an active weapon.

  7. GetWeapon (string UID): Returns the actual Weapon Component on the spawned weapon given the UID.

  8. GetActiveWeapon (): Get the actual Weapon Component on the currently spawned weapon.

  9. UnequipWeapon(): Unequip the weapon the Player is currently holding.

  10. DirectEquip(): Equip the weapon that the currentlyHoldingIndex is on.

  11. EquipWeapon (float delay): Equip the current weapon with a delay.

  12. ChangeCrosshair (Sprite crosshair): Change the crosshair of the player.

  13. RevertCrosshair (): Revert back to default crosshair.

  14. nextAvailableIndex(int from) and previousAvailableIndex(int from): Given an index, give the next/previous available weapon index.

cinemachineproperties.md

Cinemachine Properties

It's important to note that the CinemachineVirtualCamera directly controls the Camera and its properties so you won't have to work directly with the Camera in this project.

There are a lot of setting to play with here, but for a FPS based Camera, take a look at the options highlighted. These are the most important ones.

  1. Follow : By attaching the Pivot GameObject, the Camera will follow the Pivot object. A benefit of this is that you can modify the pivot position, it is not restricted to the direct center of the Player.

  2. Field Of View : Controls the Field of View property on the Camera directly.

  3. Body : This is the most important one, you will notice that the 3rd Person Follow is selected, all this does is it rotates the Camera to look at the Follow object and in the direction of the Follow object. Hence, by modifying the rotation of the Pivot GameObject we also modify the Camera rotation.

  4. Damping : By selectin ghte 3rd Person Follow we get many advantages such as the ability to add damping to the movement of the Camera, this can remove any minor jittering in the Camera and give a smooth feel, however if you want you can set it to (0,0,0) to remove it.

  5. Shoulder Offset and Vertical Arm Length : These properties can help you position your Camera relative to the Pivot GameObject, so you can make your FPS Controller appear taller than it really is without modifying the height.

Other options include the Camera Distance, by simply increasing this you now have a 3rd Person Controller, the Player Controller will function exactly the same. This is why Cinemachine is used, it is extremely simple to extend upon and change to fit what you like.

gamemanager.md

Game Manager

The GameManager is an essential component of the Classic FPS Pack, it handles spawning in the Player Controller and creating the global references for all other scripts to use. It also handles the loading and saving of scenes when the Player spawns into a new scene.

Whenever you switch between scenes you should do it through the GameManager as it will do the following things:

  1. Spawn Player Controller in Spawn Point
  2. Make Player Controller and other references easily available (ex. GameManager.PlayerController)
  3. Check if Level has existing saves and load in all the data
  4. If the Level doesn't have an existing save, create a default one so you can load back in on the last level you left off at

Important Information

The GameManager is not destroyed in between scenes, meaning the first GameManager that is found will be used. This means the properties of the GameManager that will be in the Menu scene will carry onto all the scenes from there.

In order for this to work, the GameManager must be in the root of scene, it must not be under any other GameObject in the scene.

Game Flow

The Game would start at the Main Menu scene, the GameManager would exist on this scene. The configuration here would follow through the entire game.

The Main Menu should have a way to load from the last save.

The GameState stores the last level that was loaded. The Player enters that scene and the SceneManager_sceneLoaded(Scene scene, LoadSceneMode sceneMode) function in GameManager is executed.

This loads in all the level data.

If the GameState doesnt have a previously saved Level then the GameManger will spawn the Player in the first level.

At the start of each level a new save will be created to allow the Player to 'load' back into that game.

On Player Death the health will be reset and the Player will be pushed back to the last save; this behaviour can be modified in the PlayerStatistics script.

When the Player reaches the End Scene, all saves can be cleared to allow them to restart.

This flow is implemented in the Demo Scenes folder, from the Menu to the End Screen.


Public & Static Functions

Static functions can be accessed by :

GameManager.StaticFunction()

Public functions can be accessed by :

GameManager.instance.PublicFunction()

Static

  1. LoadStartScene() : Loads the starting scene defined in the GameManager properties

  2. LoadScene (string sceneName, bool requiresLoading) : Loads the scene with a name (given it is in Build Settings) and if requiresLoading is true then it handles the Player Spawning, Saving etc. in most cases it should be true unless it is a UI scene such as 'Game Over'.

  3. RespawnFromLastSave() : Respawn from the last save the player made.

Public

  1. LoadPlayerCurrentLevel() : Loads the last scene the player was in before they left the game, this is tracked in the GameState.cs file.

Important Private Functions

  1. SceneManager_sceneLoaded(Scene scene, LoadSceneMode sceneMode) : This function is automatically called when a new scene is loaded and requiresLoading = true; here you can handle all the logic of what to do when a new scene is created.

Variables

General

Here are some other General variables that need to be assigned, the demo will have these pre-defined:

  1. playerPrefab: Reference to the FPS Controller, it's best to use the one provided with the pack.
  2. keyManager: The Key Manager scriptable object within the Project.
  3. weaponManager: The Weapon Manager scriptable object within the Project.
  4. startingSceneName: The name of the starting scene, this is used when the GameManager can't find any other saves, take a look at the Menu scene to see this being used.

Testing

When you are testing a scene you need to mark this as true in the GameManager, this will ensure the player is spawned.

public bool isTestingScene = false;

Usually the Player is spawned only when the GameManager.LoadScene function is called, however in a testing environment you might want to automatically call that; that's what this boolean is for.

Don't forget to uncheck it once you're done with it.

public bool saveSceneOnSpawn = true;
public bool loadSceneContentsOnLoad = true;

These should generally be 'true', however if you don't want to save the scene everytime the player spawns or you want to start afresh each time you can modify these variables; ensure they are both set to true when you're not testing.

Static References

You can access the following variables directly from the GameManager:

public static PlayerWeaponController PlayerWeaponController;
public static PlayerController PlayerController;
public static PlayerStatistics PlayerStatistics;
public static PlayerSFX PlayerSFX;

They can be accessed by:

GameManager.PlayerWeaponController;

This prevents you from using GameObject.FindObjectOfType too much.

keymanager.md

Key Manager

The Key Manager is a Scriptable Object meaning it is an item that lives inside your Project. You can create a new Key Manager for your project by doing the following in the project window:

Right Click > Classic FPS Pack > Key Manager

Once you have a Key Manager you can assign that in the Game Manager object.

The Key Manager just stores a list of the Keys in the game through the KeySettings class.

So when you want to create a new key just add a new element to the list within the Key Manager and modify the Key Settings to your liking.

Key Settings

The KeySettings class has the following properties:

  • keyID: The Unique ID to identify the key within the project.
  • consumable: Meaning when the key is used to open a door does it get removed from player inventory.
  • allowMultiplePickups: Can the player hold more than one of these keys up?
  • keySprite: The sprite of the Key that will show up on the UI.

Most of these properties are enforced in logic through the KeyPickup.cs script and Door.cs script. You can find the documentation for these under the Door_System namespace.

The keys the player has at any point is tracked in the PlayerStatistics.cs script.

savemanager.md

Save Manager

The Save Manager lies in the same object as the Game Manager, therefore it also doesn't get destroyed while moving between scenes.

The Save Manager doesn't have any functionality in itself, it holds a bunch of static functions that can be called to trigger saving/loading functionality.

Important

When using the Saving system, ensure that GameObjects with states attached adhere the following properties:

  1. They do not have a duplicate name to any other GameObject in the scene
  2. The UID of the State starts with the scene name followed by an '_'

    This should be the default behaviour if the State's GetUID() function is not overriden.

How it works

The topic of how saving works is very important for this pack because all components are tied back to Saving somehow.

Watch these two videos:

  1. Classic FPS Pack Saving Tutorial
  2. Saving System Coding Tutorial

The Save Manager works by the following principles:

  1. Every object that you want to save in the scene contains a class derived from the provided State.cs class
  2. The State class contains functions that tell the SaveManager how to serialize and deserialize this asset
  3. On Save the Save Manager goes through all the States in the scene and saves them to a file
  4. On Load the Save Manager goes through all the States in the scene and finds the corresponding file in the disk and loads them back in

    The process of finding which file corresponds to a given state is determined by the UID of that state.

    That is defined within the State class as well.

The Save Manager doesn't worry about what information is saved, it just handles the process of saving and loading.

The Save Manager can save to two different formats by default, Plain Text and Binary Encrypted. Plain Text is for testing and you can see what has been saved, Binary Encrypted is for production, it ensures no one tampers with the data.

Settings

These settings can be changed on the script within the GameManager GameObject.

By default the saves are located in the Application.persistentDataPath

General

Here are some other General variables that need to be assigned, the demo will have these pre-defined:

  1. binary: Whether or not it is a binary saving system.

  2. overrideSavePath: String of where you should save if you don't want to use persistent data path.

  3. usePersistentDataPath: If this is true then the overrideSavepath will be ignored.

  4. testing: If testing is true, then you can use two selected keys to load and save on demand without the need for an interaction.

    The default buttons for saving and loading are C and V. You can change these on the Save Manager script as well.

  5. ensureUniqueIds: This will throw an error instead of a warning about any duplicate IDs in the scene.

Static Functions

Static functions can be accessed by :

SaveManager.StaticFunction()

You can use these anywhere where the SaveManager is present to save/load as you need.

Loading

  1. LoadLevelContents(): Load all the states in the current scene with a small delay.

  2. LoadStateFromFile(State state, bool force): Load the state of only one file from the disk, if force is false then it will follow the rules of state.ShouldLoad()

Saving

  1. SaveAllAsync(): Save all states in the current scene asynchronously, this saves in the background but spreads it out over multiple frames as to prevent the game from lagging/stopping.

  2. SaveAllSynchronous(): Saves all states immediately before continuing the game.

  3. SaveStateToFile(State state, bool force): Save a single state to file.

  4. EnsureSafeSaving (): If you called SaveAllAsync() but the player is about to die or the scene is about to switch, ensure that the saving is completed before continuing the game.

Clearing

  1. ClearAllSaves(): Clear all the saves.

  2. ClearLevelSaves(string sceneName): Clear all the saves for the current level, note that this only works when you follow the naming convention for the States' UID.

    (gameObject.scene.name+"_"+gameObject.name+"_"+(this.GetType().Name));

    The most important part is that the states' UID starts with the scene name followed by an underscore. Everything else is optional.

    If a State is global, this does not need to be followed; global meaning the state should be loaded across multiple scenes, such as a player's health.

Querying Information

  1. GetSavingDataPath(): Where are the saves being held.

  2. ProcessedUID(State state): Returns the UID of the state after it has been processed, if binary is true then the output will be an encrypted state UID.

  3. HasSaveFiles(): Returns if has any save files at all.

  4. HasSaveFilesForScene(string scene): Returns if there are any save files for this current scene.

  5. HasSaveFilesForState(State state): Returns if there are any save files for the state.

  6. FindAllStates(): Find all the states in the scene, it is important you use this function as GameObject.FindObjectsOfType<State>() will not find disabled objects.

  7. DebugWarningsUniqueStateIDs(): This will scan the scene for any duplicate IDs and throw an error or warning about this depending on ensureUniqueIds.

sfxmanager.md

SFX Manager

The SFX Manager is located on the same object as the Game Manager. It's purpose is to handle the Ground Sounds.

Inside the SFX Manager you map all the ground sounds and in runtime you can get the footstep sound depending on the surface the player is currently standing on.

The Logic for finding what surface the player is standing on is done in the PlayerSFX script. The SFXManager just holds references and maps them to a dictionary for easy access.

To learn how to add new Ground Sounds for terrains & normal surfaces watch this tutorial:

SFX Tutorial

uimanager.md

UI Manager

The UI Manager is nothing but a collection of references to the UI. It keeps all the UI Elements in one contained location to be accessed throughout the pack.

This can be found in the root Canvas object inside the Player Controller.

It is split into 3 Sections:

  1. PlayerUI

    • crosshair (Image)
    • ammoText (Text)
    • weaponImage (Image)
    • healthText (Text)
    • coinText (Text)
  2. UIScripts

    • keyUI (Reference to KeyUI Script)
    • DialogueProcessor (Reference to DialogueProcessor script)
  3. DialogueUI

    • optionA (Button)
    • optionB (Button)
    • optionAText (Text)
    • optionBText (Text)
    • speakerText (Text)
    • text (Text)

You can access these through the following:

UIManager.Player; //PlayerUI
UIManager.Scripts; //UIScripts
UIManager.Dialogue; //DialogueUI

Public Variable

There is only one public variable:

  1. enableLoadingScreenByDefault: This is set to true by default; this refers to whether or not the loading screen for the Player Controller should be displayed by default.

weaponmanager.md

Weapon Manager

The Weapon Manager is a Scriptable Object meaning it is an item that lives in your Project. You can create a new Weapon Manager for your project by doing the following in the project window:

Right Click > Classic FPS Pack > Weapon Manager

Once you have a Weapon Manager you can assign that in the Game Manager object.

The Weapon Manager stores all the weapon references and manages the deployment of these weapons onto the player when the game is played. It also handles mapping the weapons to a keybind index for selection.

The Weapon Manager just stores a list of the Weapons in the game through the WeaponReference class.

So when you want to create a new weapon just add a new element to the list within the Weapon Manager and modify the properties inside to match your weapon.

A Weapon Reference contains the location of the prefab, the unique ID and keybind index. It is explained more in the Weapons namespace section.

The Weapon Manager is mainly invoked through the PlayerWeaponController.cs so you should interface through that most of the time. However, if you need to talk directly to the Weapon Manager here are the public functions:

Important Information

The way the Weapons are handled within the game are in the following way:

  1. You create the list of weapons in Weapon Manager

  2. You create the corresponding Weapon prefabs

    • Weapon prefabs should have a root object which has a Component deriving from the Weapon class
  3. In the Weapon Manager you point the Weapon Reference to the created prefab

  4. On Awake, the PlayerWeaponController will call the WeaponManager Setup

  5. WeaponManager will spawn all the Weapons onto the Player and initialise the properties on the Weapon class

  6. The States are loaded into the weapon (remaining ammo etc.)

  7. Input is handled in PlayerWeaponController and Equipping/Unequpping is handled through WeaponManager

Take a look at this video for better understanding of Weapons.

Public Functions / Variables

You can access a public function / variable in Weapon Manager through:

GameManager.instance.weaponManager.PublicFunction();
GameManager.instance.weaponManager.publicVariable;

Methods

  1. GetWeaponReference (string UID): Get the properties of a weapon given the ID.

  2. Equip (string UID): Unequips currently spawned weapon and equips the weapon with UID. This also calls the Equip() function on the Weapon class.

  3. Unequip (string UID): Unequips the weapon with UID, also calls the Unequip() function on the Weapon class.

  4. Setup(PlayerWeaponController weaponController): Called from PlayerWeaponController to spawn all the weapons and initialise them.

Variables

  1. spawnedWeaponInstance : Get the currently spanwed Weapon class, you can access all the information about the weapon the player is holding (ex. ammo, UI, animations etc.)

If you want to get the spawned Weapon component in the scene based on UID you can do something like this:

GameManager.instance.weaponManager.GetWeaponReference("Pistol").spawnedWeapon

This is further explained in the WeaponReference documentation.

Notes

You'll notice that the WeaponManager merely manages which weapon is spawned and loaded, the actual logic for equipping/unequipping weapons is done within the respective weapon's Weapon class.

Usually the Weapon class is extended to create classes such as the DefaultClassicFPSWeapon.

AmmoBox.md

Ammo Box

Ammo Box derives from Pickup, so it tracks the state of whether or not this item has been picked up.

Note: This item needs a trigger.

The ammo affects the weapon given the public variables and appends ammo to it, there is no set max limit to weapon ammo.

It is a prerequisite that the Player has picked up the weapon.

Once it picks up the ammo, the SFX is played, ammo is adjusted in the state of the weapon and the UI is updated.

Public Variables

  1. ID: The ID of the weapon, in the Inspector this should show as a drop down; if it doesn't then enter the ID of the weapon in the WeaponManager.

  2. isUniversalAmmo: This ammo will be appended to whatever weapon is being currently held by the Player, this will disregard the ID.

  3. pickupAmount: The amount of ammo to pickup.

CoinPickup.md

Coin Pickup

Coin Pickup derives from Pickup, so it tracks the state of whether or not this item has been picked up.

Note: This item needs a trigger.

This affects the player's coin amount in the PlayerStatistics script, then updates the UI.

Public Variables

  1. pickupAmount: Amount of coins to pickup when the player moves over it.

HealthPickup.md

Health Pickup

Health Pickup derives from Pickup, so it tracks the state of whether or not this item has been picked up.

Note: This item needs a trigger.

This affects the player's health in the PlayerStatistics script, then updates the UI.

Public Variables

  1. healthPickupAmount: Amount of health to pickup when the player moves over it, note that this will not be picked up when the Player already has max health.

Pickup.md

Pickup

The Pickup class is a base class that is extended to create other pickups such as:

  • Health Pickup
  • Weapon Pickup
  • Coin Pickup
  • Key Pickup
  • Ammo Pickup

The Pickup is also derived from State therefore it is tracked by the SavingManager.

The Pickups will usually need a Trigger Collider in order to work.

Public Variables

  1. requiresSaving: Whether or not to save the State of the pickup.

  2. allowRespawn: Should allow respawn of object even after it has been picked up (on next scene load).

  3. collectSFX: SFX to play when the item is collected.

Public Functions

These functions are designed to be called from the scripts derived from Pickup.

  1. EnableObject (bool render): This sets the Enabled state of the object, if render is false then it won't show the object and if it is true then it will.

  2. RunSFX(): Runs the collection SFX.

  3. WeaponPickupAnimation: If the weapon you have has a pickup animation, this will trigger that.

Tracked State

The only tracked state in the Pickup is enabled, in other words whether or not the object has been picked up. This ensures a health pack does not keep respawning everytime the player opens/closes the game to get infinite health.

WeaponPickup.md

Weapon Pickup

Weapon Pickup derives from Pickup, so it tracks the state of whether or not this item has been picked up.

Note: This item needs a trigger.

This appends the weapon of this pickup to the collected weapons in PlayerWeaponController, this is done with the function playerWeaponController.CollectWeapon(GunID);.

This item will only be collected when the weapon has not already been picked up; you cannot have multiple of the same Gun.

When a player picks up a weapon with an ID, all Weapon Pickups harbouring that ID will dissapear, you can change this in the CollectWeapon function on PlayerWeaponController.

Public Variables

  1. GunID: The ID of the weapon, in the Inspector this should show as a drop down; if it doesn't then enter the ID of the weapon in the WeaponManager.

GameState.md

Game State

The Transform State is based off the State class and it tracks the following information.

  • The current scene of the game

This is essentially used to track the last scene the Player was on when they left the game, this is important because only after loading them into the correct scene can we load all the states within that scene.

If this information does not exist we default to the starting scene specified in the GameManager.

Additional State Information

On Load, if the current scene is not the current active scene then it will redirect to that scene.

The most important property of this State is that ShouldLoad() is set to false, so when all states are loaded this is not included in that.

This state is manually loaded by the GameManager through the public LoadPlayerCurrentLevel() function when it is needed.

This is mainly used in the Main menu when trying to load the last player level, once the last player level is loaded then we can trust the GameManager to load the states within that level.

ISaver.md

ISaver

The Save Manager can save to two different formats by default, Plain Text and Binary Encrypted. Plain Text is for testing and you can see what has been saved, Binary Encrypted is for production, it ensures no one tampers with the data.

The way multiple saving methods can be handled is through the ISaver interface.

An ISaver is defined by the following methods:

  1. WriteFile (string path, json): Given a path and the json to write, this method should write the file to disk.

  2. string ReadFile (string path): Given a file path it should be able to read that file and output a string.

  3. string ProcessUID (string uid): Given an input UID it would process it and output a new UID.

  4. string UnprocessedUID (string processed): Given a processed UID it would reverse the process.

These 4 methods are enough to create a general template for savers.

There are two existing savers, the Binary Encrypted Saver and Simple Text Saver.

Binary Encrypted Server

This Saver should be used for production ready games, and it would be better to even create a more advanced version of this saver.

You can enable this Saver by setting binary to true on the SaveManager.

  1. WriteFile: For writing files, the writer first encrypts the JSON then writes it to file.

  2. ProcessUID: For processing UID, the saver turns the UID into Base 64 String and ensures it has no unallowed characters.

  3. ReadFile: For read file, it first decrypts the test then reads the JSON.

  4. UnprocssedUID: Simply converts Base 64 back into normal text.

The EncryptorDecryptor class is in charge of the Encryption & Decryption; it can be improved by including a better encryption algorithm.

Simple Text Saver

The Simple Text Saver is easy to use and you can read the output of these files to Debug any issues both in the naming & contents of the files.

All Methods do no additional processing to the inputs/outputs.

LevelSwitcher.md

Level Switcher

This is a trigger that the Player will stand on to move to the next level. When the Player enters the trigger the following things will happen:

  1. Checks if the Player entered the trigger
  2. Unequips the current weapon of the Player
  3. Depending on the save options, it will clear the saves of the current level or all saves
  4. It will also reset the Player's health to max depending on the options
  5. Finally, the next scene will be loaded

There is a good explanation about Scene Switching here: https://youtu.be/-r8kb0DSpSM

Public Variables

  1. sceneToSwitchTo: Name of the scene to switch to, ensure it is in the Build Settings

  2. resetPlayerHealthToMax: Whether or not to reset the Player's health to max

  3. requiresLoading: Does the next level require any loading (for normal playable scenes yes, if it is an UI screen then no).

  4. resetAll: Should destroy all saves in game.

  5. clearCurrentSceneSaves: Should wipe all the saves of the current scene (so if you go a level back then this scene will start from scratch).

SavePoint.md

Save Point

The Save Point is a MonoBehaviour that has been used on two prefabs that come with the pack:

  1. The Manual Save Point: You walk up to the save box, and press E to save; this also contains a DialogueRunner which will provide prompts.

  2. Checkpoint: You walk onto the checkpoint and it auto-saves, you can choose to make a checkpoint activate only once.

The Save Point derives from State meaning some variables within the Save Point are tracked by the SaveManager.

Public Variables

  1. isCheckpoint: Whether or not this Save Point is a Checkpoint.

  2. saveAction: The Input Key to perform the Save operation.

  3. disableCheckpointOnUse: Should disable checkpoint after standing on it.

  4. disabledMaterial: Material to replace current one on disable.

  5. onSave: SFX to play on save.

Tracked State

The only state tracked within the Save Point is whether or not a checkpoint is disabled. This ensures even after a Player respawns or reloads into a level the checkpoint remains disabled preventing re-use.

This will only be tracked if disableCheckpointOnUse is true.

SaveUtils.md

Save Utils

Save Utils contains a few functions that aid in the Saving & Loading of states. In older versions of Unity Vector3 was not able to be converted to JSON, in those cases you can use this:

SaveUtils.Vector3ToXYZ (vectorValue); //Saving Vector
SaveUtils.XYZToVector3 (value); //Loading Vector

State.md

State

This is a fundemental class used all throughout the Classic FPS Pack. Whenever anything needs to be saved/loaded you will create a class that is derived from this class.

It is a MonoBehaviour script so it will only work with component based scripts.

By extending a class from this class you will tell the SaveManager that this is a candidate for saving/loading.

To extend a class by state do something like the following:

using ClassicFPS.Saving_and_Loading;
public class TestClass : State {

}

Take a look at some common states in the pack to understand some common use cases:

  • TransformState
  • PlayerStatistics
  • GameState
  • SavePoint
  • WeaponState

Take a look at this video to understand how this system works from the ground-up and how to develop further states:

https://youtu.be/LqrdAKjftOM

How Are States Used

States are just data holders and describe how information should be converted to a string and recieved from a string. The Saving & Loading to files is done in the SaveManager.

When you 'Save All States' all it does is find all the states in the current scene and calls the SaveState() method and stores the returned string into a file.

Then when you load it, we read that file and pass it back into the state. In this way all the logic is self contained in the state itself.

Override Functions

In order to develop the logic to utilise these states you will need to override some of the following functions.

You can override a function like so (ex. SaveSate):

public override string SaveState()
{
    return "";
}
  1. string GetUID(): In this function you can return a string that identifies this state uniquely. This is extremely important. This string will be used to identify which file to read back into this state.

    This means your UID should strive to exist only once in a given scene. The default UID is constructed in the following format:

    (gameObject.scene.name+"_"+gameObject.name+"_"+(this.GetType().Name));

    This will result in something like this: TestScene_Cube_TransformState, the benefit of this is that this state will only be tracked for the 'TestScene' as this UID is scene dependent.

    If you want a scene independent state such as Player health (in PlayerStatistics); you must remove the gameObject.scene.name section; leaving just Cube_TransformState. This will be loaded in no matter the scene.

    Since we are using gameObject.name as the default, it is possible to have multiple GameObjects with the same name in the same scene; this should be avoided. Or a custom UID can be defined for objects by overriding this method.

  2. string SaveState(): This function when called should return the serialized string state of this class. This same string when given back to the State should be able to retrieve the needed information from it.

    A common data format used to do this is the JSON format.

    Most of the States in Classic FPS Pack use Unity's JSON Utility

  3. void LoadState (string loadedJSON): This function takes back in some string (doesn't have to be JSON); and decides what to do with that information.

    The general course of action would be to convert the JSON back into the object it was created from; then set that information into the game.

  4. void EmptyLoadState (): This function is called whenever this state is loaded but there was no save found for it; this can be some initialization behaviour.

  5. bool ShouldSave (): Here you can write logic telling the Save Manager when it should or shouldn't save this current state. By default this always returns true.

    For example in the transform state, we set this to false if the object has not moved, this saves space and time as we don't need to save something that hasn't changed.

  6. bool ShouldLoad(): Similar to ShouldSave(), you can decide when this state should allow to be loaded in or when it shouldn't.

Creating a new State

In order to create a new state you will need some data to track; this will have to be created by you. This data must be 'serializable', so it cannot be things such as Meshes, GameObjects etc.

It has to be pure data that can somehow be converted to a string, this is doable for most primitives and arrays of primitives.

Checkout this very simple state that tracks the damage on a player or object:

//1
public class DamageState : State {

    [System.Serializable] //2
    struct DamageStateData {
        //3
        public float health;
        public string currentState;
    }

    DamageStateData storedData; //4

    //5
    public void TakeDamage ()
    {
        storedData.health -= 10;

        if (storedData.health < 50) storedData.currentState = "Damaged";

        if (storedData.health < 20) storedData.currentState = "Heavily Damaged";

        if (storedData.health <= 0) {
            storedData.currentState = "Dead";
            GameObject.Destroy(this.gameObject);
        }

    }

    //6
    public override string SaveState()
    {
        return SaveUtils.ReturnJSONFromObject<DamageStateData>(savedState);
    }

    //7
    public override void LoadState (string loadedJSON)
    {
        storedData = SaveUtils.ReturnStateFromJSON<DamageStateData>(loadedJSON);

        //8
        if (storedData.health <= 0) {
            GameObject.Destroy(this.gameObject);
        }
    }

}

Lets break it down based on section:

  1. Create a state extending from State

  2. Create a struct type with any name and mark it as [System.Serializable] to allow to serialize the data within this struct.

  3. Have some properties within this struct that are serializable

  4. Create a variable that utilises the struct such as storedData

  5. This is an example function that makes the Object take some damage and then changes the health and currentState properties; it also has some logic so when health is < 0 it destroys the GameObject.

  6. When SaveState() is called, I use SaveUtils to convert the struct to JSON data; you can use JsonUtility directly.

    In the background this is really just calling:

     public static string ReturnJSONFromObject<T>(T state)
     {
         string json = JsonUtility.ToJson(state);
         return json;
     }
  7. When LoadState() is called, I load the storedData from the input JSON.

  8. As soon as I have the needed information loaded in, I can perform the logic I need. For example here, if something was killed then it should remain dead; so if health is < 0 after it is loaded then we should destroy it.

That's how simple it is to create your own states!

TransformState.md

Transform State

The Transform State is based off the State class and it tracks the following information.

Normal Objects:

  1. Position
  2. Rotation
  3. Scale
  4. Rigidbody Velocity
  5. Rigidbody Angular Velocity

If it is a Player Controller, it tracks also:

  1. Camera Direction
  2. Player Position (handles teleportation on load)

Additional State Information

The TransformState utilises ShouldSave() override, it only saves when the position/rotation has changed to save space.

When the Player Controller's transform is loaded this specific code is executed:

GetComponent<PlayerController>().TeleportPlayer(SaveUtils.XYZToVector3(savedState.positionState));
GetComponent<PlayerCameraController>().SetRotation(SaveUtils.XYZToVector3(savedState.rotationState));

The TeleportPlayer and SetRotation function on the respective scripts interrupt the flow of the Player Controller to inject this behaviour.

scripting_docs.md

Scripting Documentation

The Classic FPS Pack contains multiple namespaces under which the different scripts are held, the content will be split based on the namespaces.

Provided Namespaces

Managers

Contains manager scripts that control the behaviour of different systems in the game, include by adding the following:

using ClassicFPS.Managers;

Scripts in Namespace:


Controller

Contains core controller scripts, these dictate movement & interaction of the Player Controller in the game.

using ClassicFPS.Controller;

Scripts in Namespace:


Weapons

Contains all the scripts that dictate the actions of the weapons before/during/after equipped.

using ClassicFPS.Weapons;

Abstract/General

Demo Scripts


Audio

Classes responsible for handling audio within all the other components of the pack, scripts also responsible for footstep ground detection on terrains.

using ClassicFPS.Audio;

Scripts in Namespace:


Saving and Loading

Classes responsible for saving and loading the states of the game, also contains some general states that can be applied to objects.

using ClassicFPS.Saving_and_Loading;

Abstract/General

Demo Scripts


Interactable Objects

Classes responsible for handling interactable objects for example things that can be pushed, thrown or broken.

using ClassicFPS.Interactable_Objects;

Enemy

Classes that contains all the scripts that manage Enemy movement, interaction and saving.

using ClassicFPS.Enemy;

Scripts in Namespace:


Door System

Classes that control opening Doors w/ key pickups and also dialogue interactions to get keys from NPCs.

using ClassicFPS.Door_System;

Scripts in Namespace:


Dialogue System

Scripts used for the Dialogue System, all the way from parsing serialized dialogue to interacting with it and showing it on UI.

using ClassicFPS.Dialogue_System;

Scripts in Namespace:


Pickups

General Pickup items that are used to modify the state of the Player.

using ClassicFPS.Pickups;

Scripts in Namespace:


UI

General UI Scripts to interact with different systems in the pack.

using ClassicFPS.UI;

Scripts in Namespace:


Utils

Only contains one script, this is used by both Gravity Gun and PlayerObjectInteractionHandler to handle pickup up objects.

using ClassicFPS.Utils;

Scripts in Namespace:

PickupUtils.md

Pickup Utils

The Pickup Utils are used by both the Player Object Interaction Handler and the Gravity Gun to handle the picking up of external objects in the scene.

This script is executed in conjuction with some logic in the respective scripts that execute it.

Public Variables

  1. pickupSpeed: Speed at which the picked up object goes to the position infront of the Player.

  2. throwSpeed: Speed of the throw when the player releases the object.

  3. coverViewportPercentage: This is the amount of the viewport to fill up with the object; this value is used to pickup objects of various sizes while keeping it near to constant on the screen.

  4. maximumDamage: Maximum damage that can be delivered from throwing this object.

  5. defaultCrosshairColor: Normal crosshair color is white.

  6. selectedCrosshairColor: Color of crosshair when you hover over an object that can be picked up.

  7. clippingObjectThreshold: Amount of the object that can be clipped into the ground before it is retracted away from it.

  8. dropFromExpectedPositionLerp: If the object is greater than this distance away from the expected position when the player attempts a throw, this will execute a drop instead.

  9. discludePlayer: A layermask discluding the player to pickup & throw.

Public Functions

  1. Setup(Camera playerCamera): The script executing this should call the Setup to setup the initial parameters.

  2. OnPickup (Transform transform, MonoBehaviour from): When an object is picked up pass in the transform and the script that is executing this logic.

  3. ResetObject(): Resets the picked up object including its Rigidbody parameters, colliders etc.

  4. ModifyCrosshair (): Based on the raycast from the crosshair position, the object highlighted will modify the crosshair and scaling of the crosshair.

  5. UpdateLoop (): This update loop function should be called on Update from the script executing this utility script.

BaseWeapon.md

Base Weapon

The Base Weapon is derived from the Weapon class. The Base Weapon is less abstract than the Weapon class and manages some default functionality such as managing the animator, handling unequipping/equipping logic and opening up some other general functionality.

Responsibilities

The responsibilities of this script are to provide some common functionality that can be used in child classes, and to handle the equipping/unequipping logic especially in terms of animations.

Public Variables

The Base Weapon is what the default weapons provided in the pack are based off. In addition to the settings described in the Weapon class, there are a few additional settings in the BaseWeapon.

Here are the public variables you can tweak to modify the weapon behaviour, these should be done on the prefab.

  1. shootSounds: A list of sounds that will be emitted at random from the gun on shoot.

  2. emptyAmmoSound: Sound to play when Player shoots without any ammo.

  3. disableDelay: The delay before weapon is disabled (allows disable animation to play)

  4. enableDelay: Delay before weapon is fully enabled and allowed to shoot (allows enable animation to play)

  5. disableBeforeEnableAnimation: The time to wait before playing enable animation, allows previous weapon to disable before starting this weapon's animation.

Override Methods

Note that BaseWeapon itself is a base class and should be overriden by other classes.

Some methods to override are:

  1. OnGunEquipped(): What to do after the weapon has been equipped fully (animation played & completed).
  2. OnGunUnequipped(): What to do after the weapon is completely unequipped.

Protected Methods

Methods to call from derived classes of BaseWeapon.

  1. ToggleVisibility (bool value): Visibility of all mesh renderers beneath the Weapon.

  2. SetState (WeaponState newState): Allows you to change the state of the current weapon.

  3. RunShootAnimation(): Runs the shoot animation and the sound associated with shooting.

  4. RunNoAmmoAnimation(): Runs no ammo animation and SFX.

  5. HandlePlayerAnimate(): This function should be called from the Update() function of any class deriving from BaseWeapon, it will determine when to play the walk animation & the speed of the walking animation.

  6. HandlePlayerPickup(): What do do when the player picks up an item.

  7. ResetAnimator(): Disable the animator for the current weapon/reset it.

DefaultClassicFPSWeapon.md

Default Classic FPS Weapon

The Default Classic FPS Weapon is used in all the demo weapons except the Gravity Gun. It is also derived from the BaseWeapon class.

General Public Variables

  • shooting: DefaultClassicFPSWeapon_Shooting is a class that stores all the logic/variables related to the shooting of these weapons. It was used to seperate the shooting logic from the input logic.

  • shootAction: Input to begin the shooting

  • shootingThroughAnimation: If this is 'true' then using the shootAction will only begin the Animation. The Animation should trigger an event to call the AnimatorShoot() function in this script.

    This is useful for things such as an Axe where the attack should be performed only when the axe lands on the enemy.

  • shootDelay: Delay between attacks

  • holdShoot: Can you shoot multiple times when the shootAction remains pressed.

Shooting Public Variables

The Shooting Public variables are under the shooting variable.

  • hasLimitedRange: Does this gun have a limited shooting range, meaning after a certain distance no more damage will be dealt.

  • maxShootDistance: How far can this gun shoot, if it has unlimited range then the maxShootDistance will control the amount of damage the enemy recieves. If you always want it to be the max amount of damage, se this to 1.

  • useSphereCast: Use a Sphere Cast instead of a Raycast.

  • sphereCastRadius: Radius of Sphere Cast.

  • hitMask: All layers that can be hit from this weapon.

  • shootsProjectiles: Does this weapon shoot projectiles? Only used in the Rocket Launcher.

  • projectilePrefab: Prefab of the Projectile.

  • projectileStartPosition: Start position of the projectile, this is actually a reference to an empty Transform inside your Weapon prefab that indicates the starting position/rotation of the the projectile.

  • projectileSpeed: Initial speed of the projectile, this assumes the projectile has a Rigidbody.

  • forwardForce: Forward force imparted on any rigidbody the Raycast shot from this gun produces.

  • maximumDamage: Maximum damage that this weapon can impart on a DamageableEntity.

  • minimumDamage: The minimum damage that this weapon can impart on a DamageableEntity.

  • scatter: Should this weapon scatter its bullets (shotgun).

  • scatterBulletsInEachDirection: Amount of bullets to scatter.

  • scatterRadius: Radius of scatter.

  • onHitObjectSFX: What SFX to play when this object hits any surface (ground, wall etc.). If there exists a Pushable Object script with a custom SFX that will be played instead.

Shooting Public Functions

  1. TakeSingleShot (Camera camera, Vector2 crosshairPosition): Take a single shot from a given crosshair position.

  2. CreateRaycast(ref Ray ray): Creates raycast given gun properties.

  3. ShotRay(Ray ray, RaycastHit hit): Shot that ray, and all the different object types are handled in this function:

    • Handle shooting Rigidbody
    • Handle shooting Pushable Object
    • Handle shooting Damageable Entity (Breakable Object, Enemies)
    • Handle shooting Projectile

    You can change this function to create different behaviour given the information in the rayast hit.

  4. AnimatorShoot(): This function is used when the animator calls the AnimatorShoot() function.

Other Public Functions

  1. Attack(Camera camera, Vector2 crosshairPosition): This is the general attack function, this is called directly from the inputs. This triggers the animation as well as changes the ammo count.

    This also ensures that no shooting is conducted if the weapon has run out of ammo.

Functionality

On Update() the weapon shoots automatically if the Player is holding down the shoot button; it also handles Player animation such as walking.

OnGunEquipped() and OnGunUnequipped() handle the registering/deregistering of Inputs and their link to start attacks.

Note that this script is derived from BaseWeapon so all equipping logic, animation logic and unequipping logic is handled there.

DemoGravityGun.md

Demo Gravity Gun

This weapon is included directly in the pack in the form of a Gravity Gun. It is derived from the BaseWeapon class.

The Gravity Gun can pickup items that have the PushableObject script with the canPickup variable set to true.

Public Variables

  1. gravityGun: This is variable is based of a class called PickupUtils, this script manages all the pickup behaviour; and the variables within this class modify the pickup behaviour.

  2. pickupAction: The key/mouse button to press to pickup the object with Gravity Gun. This is an 'InputAction' asset which is a part of the new Input System, it allows you to find actions directly in the inspector.

  3. throwAction: Action used to throw the object with force. To drop the object down you can use the same action as the pickupAction.

  4. releaseSound: Sound when item is released.

  5. cantPickupOrThrowSound: Sound when you try to pickup and object that is not throwable.

The PickupUtils class is also used in the PlayerObjectInteractionHandler to manage picking up items without a Gravity Gun.

Go to PickupUtils Scripting Reference to learn how to change all the pickup properties such as pickup distance, crosshair color etc.

Most of the pickup/throwing logic is done in the PickupUtils script, the only thing handled in this script is the Input logic.

Note that this script is derived from BaseWeapon so all equipping logic, animation logic and unequipping logic is handled there.

Projectile.md

Projectile

The Projectile script is attached to all projectiles within the Classic FPS Pack, mainly in the following instances:

  1. Player Rocket Launcher Projectile
  2. Enemy Bullet Projectile
  3. Enemy Homing Missile Projectile

Layers

When you use the Classic FPS Setup Editor the following layers and properties are imported:

  1. "Projectile" layer
    • Cannot collide with other Projectiles
  2. "Enemy Projectile" layer
    • Cannot collide with other Enemy Projectiles
    • Cannot collide with Enemies

Public Variables

These variables can be modified to change the behaviour of the projectile.

  1. damage: The damage that the Projectile does on impact.

  2. canHarmPlayer: Can cause damage to the Player.

  3. canHarmEnemies: Can cause damage to Enemies.

  4. doesExplode: Does it affect nearby objects on impact/destory.

  5. explosionForce: Force of explosion onto Rigidbodies.

  6. upwardsForce: How much upwards force does the explosion cause.

  7. explosionRadius: Radius at which Rigidbodies are affected by the projectile.

  8. minimumImpactVelocity: Minimum velocity to impart onto Rigidbodies on contact.

  9. minimumVelocityBeforeDestruction: Minimum velocity before the projectile destroys itself because it is coming to a stop.

  10. stickToImpact: Stick to the first surface the Rigidbody impacts; if this is a sticking projectile, then the collider should be a trigger.

  11. timeToImpact: Time to destroy after sticking to surface.

  12. isHomingMissile: Is this projectile a homing missile (will follow the player).

  13. destroyAfter: Time after to destroy the projectile by default.

  14. moveSpeed: Speed at which to chase the player.

  15. explosionSound: SFX of explosion.

Functionality

On Collision Enter the Projectile will explode and call the Impact function which will handle all the different cases of explosions:

  1. Players nearby - Destroy/Damage them
  2. Enemies nearby - Destroy/Damage them
  3. Pushable Objects nearby - Move them away
  4. Breakable Objects nearby - Destroy/Damage them
  5. Rigidbodies - Move them away

On the Update method the projectile follows the player if it is a homing missile. It also checks if the Projectile drops velocity below minimumVelocityBeforeDestruction. if so it will detonate.

Weapon.md

Weapon

The weapon class is a template for which all Weapons in the Classic FPS Pack should be based on. It provides base settings and empty methods to override when creating new weapons.

It is a MonoBehaviour script and any class derived from Weapon should lie on the root GameObject of the Weapon prefab.

The Weapon also is derived from State, this means that every weapon will be tracked by the SaveManager.

The UID of the Weapon State is the same as the UID of the Weapon.

General

On Awake, the Player Weapon Controller will call the Weapon Manager which will spawn all the Weapons and call the Setup() method.

The default Setup() method sets the UID of the Weapon & Loads any states of the weapon manually.

Public Variables

The UID of a Weapon is set in the WeaponManager.

To access a Weapon's public variable you should access it through the Player Weapon Controller:

//Getting pistol weapon
GameManager.PlayerWeaponController.GetWeapon ("Pistol").publicVariable;

There is only one key public variable inside the Weapon Class:

  1. settings: This is a WeaponSettings class and it contains the core settings that every weapon needs to include for the UI & Logic to be functional.

Settings

These are the settings inside the settings variable:

  1. overridePicking: Whether or not to disable default object pickup while using this weapon, this will be set to false unless it is a weapon like Gravity Gun.

  2. requiresAmmo: Does this weapon require tracking of ammo?

  3. defaultAmmo: Default ammo amount.

  4. thumbnail: The icon for the weapon that will show up in the bottom right corner of the UI.

  5. crosshair: The icon for the crosshair when this weapon is equipped.

  6. crosshairSize: The size of the crosshair when it is created.

  7. crosshairScaleOnTarget: How much to scale the crosshair when it is hovering over an object to pickup.

Tracked State

The Weapon is also constantly tracking its own state so when it is time to save and load it has the data needed.

The state is tracked in the weaponState variable, there are only two things tracked for the default weapon; more can be added in derived classes by overriding SaveState() and LoadState().

The Weapon State tracks:

  • UID of the Weapon
  • ammoRemaining

Whenever the shooting action is triggered ensure to modify the state so that it is tracked.

Override Functions

These functions should be overriden by classes that are derived from the Weapon class, for example the BaseWeapon.

  1. Equip (string UID): This function is called when the weapon is equipped, any equipping logic should be done here.

  2. Unequip (): This function is called when the weapon is unequipped.

WeaponAttribute.md

Weapon Attribute

If you want to reference a weapon anywhere in your game you will need to know the UID of the Weapon.

If you have 1 Weapon Manager in the project then you can use the Weapon Attribute to turn a string into a popup of available Weapon UIDs.

This will make it easier to reference weapons and change them in future.

This can be done by using the following:

using ClassicFPS.Weapons;

[WeaponID]
public string weaponReference;

WeaponReference.md

Weapon Reference

The Weapon Reference is used in the Weapon Manager as a way to represent Weapons before they have been spawned into the game.

Core Propreties

  1. WeaponUniqueID: The UID of the Weapon that is used everywhere to represent the Weapon.
  2. WeaponPrefab: The Weapon Prefab that is linked to the UID, the prefab should contain Weapon component in it's root.
  3. keyBindNumber: Number to link to the weapon (0-9), if it is -1 it won't be included in keyBind.

    It will still work with Scroll Wheel without key bind. You can also attach a custom way to equip this weapon through looking at the PlayerWeaponController documentation.